package self.hoy.demo.grainmall.search.service.impl;

import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import self.hoy.demo.common.to.es.SpuEsModel;
import self.hoy.demo.grainmall.search.config.GrainElasticSearchConfiguration;
import self.hoy.demo.grainmall.search.service.MallSearchService;
import self.hoy.demo.grainmall.search.vo.SearchParam;
import self.hoy.demo.grainmall.search.vo.SearchResult;

import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

/**
 * @author hyh
 * @date 2020/7/22
 */
@Slf4j
@Service
public class MallSearchServiceImpl implements MallSearchService {
    /**
     * 以下这些产量用于聚合搜索等，拼装搜索请求和解析搜索结果需要用一致的值，所以提取
     */
    private static final String CATALOG_AGG = "category_agg";
    private static final String CATALOG_NAME_AGG = "catalog_name_agg";
    private static final String BRAND_AGG = "brand_agg";
    private static final String BRAND_NAME_AGG = "brand_name_agg";
    private static final String BRAND_IMG_AGG = "brand_image_agg";
    private static final String ATTR_AGG = "attr_agg";
    private static final String ATTR_ID_AGG = "attr_id_agg";
    private static final String ATTR_NAME_AGG = "attr_name_agg";
    private static final String ATTR_VALUE_AGG = "attr_value_agg";

    @Autowired
    private RestHighLevelClient restHighLevelClient;

    /**
     * 搜索引擎中索引库的名称
     */
    @Value("${search.index.product:grain_product}")
    private String index;

    @Value("${search.index.pageSize:2}")
    private Integer pageSize;

    /**
     * 1、动态构建查询需要的DSL语句
     * 2、执行检索请求
     * 3、分析响应数据，封装成目标格式
     * @param searchParam
     * @return
     */
    @Override
    public SearchResult search(SearchParam searchParam) {
        SearchRequest request = buildSearchRequest(searchParam);
        try {
            SearchResponse response = restHighLevelClient.search(request, GrainElasticSearchConfiguration.COMMON_OPTIONS);
            return parseSearchResponse(response, searchParam);
        } catch (IOException e) {
            SearchResult result = new SearchResult();
            log.error("", e);
            return result;
        }
    }

    /**
     * 模糊匹配，过滤（按照属性、分类、品牌、价格区间、库存）
     * @param param
     * @return
     */
    private SearchRequest buildSearchRequest(SearchParam param) {
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();

        if (!StringUtils.isEmpty(param.getKeyword())) {
        //1.1、must模糊匹配
            boolQuery.must(QueryBuilders.matchQuery("skuTitle", param.getKeyword()));

            //高亮
            HighlightBuilder builder = new HighlightBuilder();
            builder.field("skuTitle");
            builder.preTags("<b style='color:red'>");
            builder.postTags("</b>");
            sourceBuilder.highlighter(builder);
        }

        //1.2.1、按三级分类id过滤
        if (param.getCatalog3Id() != null) {
            boolQuery.filter(QueryBuilders.termQuery("catalogId", param.getCatalog3Id()));
        }

        //1.2.2 按照品牌id过滤
        if (!CollectionUtils.isEmpty(param.getBrandId())) {
            boolQuery.filter(QueryBuilders.termsQuery("brandId", param.getBrandId()));
        }

        //1.3 按照属性
        if (!CollectionUtils.isEmpty(param.getAttrs())) {
            for (String attrStr : param.getAttrs()) {
                BoolQueryBuilder nestedBoolQuery = QueryBuilders.boolQuery();
                String[] s = attrStr.split("_");
                String attrId = s[0];
                String[] values = s[1].split(":");
                nestedBoolQuery.must(QueryBuilders.termQuery("attrs.attrId", attrId));
                nestedBoolQuery.must(QueryBuilders.termsQuery("attrs.attrValue", values));
                NestedQueryBuilder nestedQuery = QueryBuilders.nestedQuery("attrs", nestedBoolQuery, ScoreMode.None);
                boolQuery.filter(nestedQuery);
            }
        }

        //1.4 库存
        if (param.getHasStock() != null) {
            boolQuery.filter(QueryBuilders.termQuery("hasStock", param.getHasStock().intValue() == 1));
        }

        //1.5 价格区间
        if (!StringUtils.isEmpty(param.getSkuPrice())) {
            RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("skuPrice");
            String[] s = param.getSkuPrice().split("_");
            if (s.length == 2) {
                rangeQuery.gte(s[0]).lte(s[1]);
                boolQuery.filter(rangeQuery);
            } else if (param.getSkuPrice().startsWith("_")) {
                rangeQuery.lte(s[0]);
                boolQuery.filter(rangeQuery);
            } else if (param.getSkuPrice().endsWith("_")) {
                rangeQuery.gte(s[0]);
                boolQuery.filter(rangeQuery);
            }
        }

        sourceBuilder.query(boolQuery);

        //2.1 排序
        String sort = param.getSort();
        if (!StringUtils.isEmpty(sort)) {
            String[] s = sort.split("_");
            sourceBuilder.sort(s[0], "asc".equalsIgnoreCase(s[1]) ? SortOrder.ASC : SortOrder.DESC);
        }

        //分页
        sourceBuilder.from((param.getPageNum() - 1) * pageSize);
        sourceBuilder.size(pageSize);

        // 聚合
        // 品牌聚合
        TermsAggregationBuilder brandAgg = AggregationBuilders.terms(BRAND_AGG);
        brandAgg.field("brandId").size(50);
        //品牌子聚合1 品牌名
        brandAgg.subAggregation(AggregationBuilders.terms(BRAND_NAME_AGG).field("brandName").size(1));
        //品牌子聚合2 品牌图
        brandAgg.subAggregation(AggregationBuilders.terms(BRAND_IMG_AGG).field("brandImg").size(1));
        sourceBuilder.aggregation(brandAgg);

        // 分类聚合
        TermsAggregationBuilder categoryAgg = AggregationBuilders.terms(CATALOG_AGG).field("catalogId").size(50);
        brandAgg.field("catalogId").size(50);
//        分类子聚合
        categoryAgg.subAggregation(AggregationBuilders.terms(CATALOG_NAME_AGG).field("catalogName").size(1));
        sourceBuilder.aggregation(categoryAgg);

        // 属性聚合
        NestedAggregationBuilder attrAgg = AggregationBuilders.nested(ATTR_AGG, "attrs");
        TermsAggregationBuilder attrIdAgg = AggregationBuilders.terms(ATTR_ID_AGG).field("attrs.attrId").size(10);
        attrIdAgg.subAggregation(AggregationBuilders.terms(ATTR_NAME_AGG).field("attrs.attrName").size(1));
        attrIdAgg.subAggregation(AggregationBuilders.terms(ATTR_VALUE_AGG).field("attrs.attrValue").size(50));
        attrAgg.subAggregation(attrIdAgg);
        sourceBuilder.aggregation(attrAgg);

        log.info(sourceBuilder.toString());
        return new SearchRequest(new String[]{index}, sourceBuilder);
    }

    /**
     *
     * @param response
     * @param searchParam
     * @return
     */
    private SearchResult parseSearchResponse(SearchResponse response, SearchParam searchParam) {
        SearchHits hits = response.getHits();
        SearchResult result = new SearchResult();
        //当前查询到的所有商品信息
        if (hits.getHits() != null && hits.getHits().length > 0) {
            List<SpuEsModel> modelList = new LinkedList<>();
            for (SearchHit hit : hits.getHits()) {
                String sourceAsString = hit.getSourceAsString();
                SpuEsModel model = JSON.parseObject(sourceAsString, SpuEsModel.class);
                HighlightField field = hit.getHighlightFields().get("skuTitle");
                String title = field == null ? null : field.getFragments()[0].string();
                if (title != null) {
                    model.setSkuTitle(title);
                }
                modelList.add(model);
            }
            result.setProducts(modelList);
        }

        //当前所有商品涉及到的所有属性信息：
        ParsedNested attrAgg = response.getAggregations().get(ATTR_AGG);
        List<SearchResult.AttrVo> attrVos = new LinkedList<>();
        ParsedLongTerms attrIdAgg = attrAgg.getAggregations().get(ATTR_ID_AGG);
        for (Terms.Bucket bucket : attrIdAgg.getBuckets()) {
            ParsedStringTerms attrNameAgg = bucket.getAggregations().get(ATTR_NAME_AGG);
            ParsedStringTerms attrValueAgg = bucket.getAggregations().get(ATTR_VALUE_AGG);
            List<String> attrValue = new ArrayList<>();
            for (Terms.Bucket valueBucket : attrValueAgg.getBuckets()) {
                attrValue.add(valueBucket.getKeyAsString());
            }
            SearchResult.AttrVo vo = new SearchResult.AttrVo();
            vo.setAttrId(bucket.getKeyAsNumber().longValue());
            vo.setAttrName(attrNameAgg.getBuckets().get(0).getKeyAsString());
            vo.setAttrValue(attrValue);
            attrVos.add(vo);
        }
        result.setAttrs(attrVos);

//        当前商品涉及到的所有分类信息
        ParsedLongTerms catalogAgg = response.getAggregations().get(CATALOG_AGG);
        List<SearchResult.CategoryVo> categoryVos = new LinkedList<>();
        for (Terms.Bucket bucket : catalogAgg.getBuckets()) {
            SearchResult.CategoryVo vo = new SearchResult.CategoryVo();
            vo.setCatalogId(bucket.getKeyAsNumber().longValue());
            ParsedStringTerms catalogNameAgg = bucket.getAggregations().get(CATALOG_NAME_AGG);
            vo.setCatalogName(catalogNameAgg.getBuckets().get(0).getKeyAsString());
            categoryVos.add(vo);
        }
        result.setCatalogs(categoryVos);

//        当前商品涉及到的所有品牌信息
        ParsedLongTerms brandAgg = response.getAggregations().get(BRAND_AGG);
        List<SearchResult.BrandVo> brandVos = new LinkedList<>();
        for (Terms.Bucket bucket : brandAgg.getBuckets()) {
            SearchResult.BrandVo vo = new SearchResult.BrandVo();
            vo.setBrandId(bucket.getKeyAsNumber().longValue());
            ParsedStringTerms brandNameAgg = bucket.getAggregations().get(BRAND_NAME_AGG);
            vo.setBrandName(brandNameAgg.getBuckets().get(0).getKeyAsString());
            ParsedStringTerms brandImgAgg = bucket.getAggregations().get(BRAND_IMG_AGG);
            vo.setBrandImg(brandImgAgg.getBuckets().get(0).getKeyAsString());
            brandVos.add(vo);
        }
        result.setBrands(brandVos);

        result.setPageNum(searchParam.getPageNum());
        long total = hits.getTotalHits().value;//总记录数
        result.setTotalPage((int)(total / pageSize + (total % pageSize == 0 ? 0 : 1)));
        result.setTotal(total);
        return result;
    }

}
